﻿//-----------------------------------------------------------------------
// <copyright file="JustifyRuleViolationSuppressions.cs">
//     Copyright (c) Nicole Calinoiu. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Diagnostics.CodeAnalysis;

using Microsoft.FxCop.Sdk;
using Microsoft.VisualStudio.CodeAnalysis.Extensibility;

using Bordecal.FxCop.Rules.Usage.Configuration;
using Bordecal.FxCop.Sdk;

namespace Bordecal.FxCop.Rules.Usage
{
	/// <summary>
	/// Rule that verifies that a <see cref="SuppressMessageAttribute"/> instance has a non-empty
	/// <see cref="M:SuppressMessageAttribute.Justification"/> value.
	/// </summary>
	internal sealed class JustifyRuleViolationSuppressions : ConfigurableIntrospectionRule<JustifyRuleViolationSuppressionsConfiguration>
	{
		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="JustifyRuleViolationSuppressions"/> class.
		/// </summary>
		public JustifyRuleViolationSuppressions()
			: base(RuleDescriptorFactory.CreateSingleResolutionDescriptor<JustifyRuleViolationSuppressions>(
				RuleCategory.Usage,
				"Justify rule violation suppressions",
				"When suppressing an FxCop rule violation, a meaningful explanation for the exclusion should be provided.",
				95,
				FixCategories.NonBreaking,
				MessageLevel.Warning,
				"Use the Justification property of the {0} attribute to explain the exclusion."))
		{
		}

		#endregion

		#region Methods

		/// <summary>
		/// Verifies compliance of a type member with the implemented rule.
		/// </summary>
		/// <param name="member">The target member.</param>
		/// <returns>A collection of found problems.</returns>
		public override ProblemCollection Check(Member member)
		{
			if (member == null)
			{
				throw new ArgumentNullException("member");
			}

			this.VisitAttributes(member.Attributes);
			return this.Problems;
		}

		/// <summary>
		/// Verifies compliance of a module with the implemented rule.
		/// </summary>
		/// <param name="module">The target module.</param>
		/// <returns>A collection of found problems.</returns>
		public override ProblemCollection Check(ModuleNode module)
		{
			if (module == null)
			{
				throw new ArgumentNullException("module");
			}

			this.VisitAttributes(module.Attributes);
			return this.Problems;
		}

		/// <summary>
		/// Verifies compliance of a method parameter with the implemented rule.
		/// </summary>
		/// <param name="parameter">The target parameter.</param>
		/// <returns>A collection of found problems.</returns>
		public override ProblemCollection Check(Parameter parameter)
		{
			if (parameter == null)
			{
				throw new ArgumentNullException("parameter");
			}

			this.VisitAttributes(parameter.Attributes);
			return this.Problems;
		}

		/// <summary>
		/// Verifies compliance of a type with the implemented rule.
		/// </summary>
		/// <param name="type">The target type.</param>
		/// <returns>A collection of found problems.</returns>
		public override ProblemCollection Check(TypeNode type)
		{
			if (type == null)
			{
				throw new ArgumentNullException("type");
			}

			this.VisitAttributes(type.Attributes);
			return this.Problems;
		}

		/// <summary>
		/// Visits the attribute node.
		/// </summary>
		/// <param name="attribute">The attribute.</param>
		public override void VisitAttributeNode(AttributeNode attribute)
		{
			if (attribute == null)
			{
				throw new ArgumentNullException("attribute");
			}

			if (attribute.Type == FrameworkTypes.SuppressMessageAttribute)
			{
				string justification = JustifyRuleViolationSuppressions.GetJustification(attribute).Trim();
				if (justification.Length < this.CurrentConfiguration.MinimumJustificationLength)
				{
					var checkId = attribute.GetPositionalArgumentValue<string>(1);
					this.AddProblem(attribute, checkId, FixCategories.NonBreaking, RuleUtilities.Format(attribute));
				}
			}

			base.VisitAttributeNode(attribute);
		}

		private static string GetJustification(AttributeNode attribute)
		{
			string result = string.Empty;

			foreach (Expression expression in attribute.Expressions)
			{
				NamedArgument argument = expression as NamedArgument;
				if ((argument != null) && string.Equals(argument.Name.Name, "Justification", StringComparison.Ordinal))
				{
					Literal justificationLiteral = argument.Value as Literal;

					if (justificationLiteral != null)
					{
						result = (string)justificationLiteral.Value ?? string.Empty;
					}

					break;
				}
			}

			return result;
		}

		#endregion
	}
}